using System;
using System.Collections;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;
using System.Xml;
using System.Net.Mail;
using System.Net;
using System.Timers;

using WeatherRouter.Model;
using WeatherRouter.Dispatcher;

namespace WeatherRouter
{
    /// <summary>
    /// </summary>
    public class Engine
    {        
        private Timer _timer;
        
        public Engine()
        {
        }

        public void Init()
        {
            LogManager.Info("Begin engine initialization");

            CheckDebugger();

            Configuration.Init();
            
            Cache.Init();
            
            InitializeScheduler(); 

            LogManager.Info("End engine initialization");
        }
        
        /// <summary>
        /// Checks if debugger is not attached and executed not in interactive mode,
        /// put thread into sleeping mode allowing put breakpoints at early stages of loading.
        /// This is useful for debugging when running as service.
        /// </summary>
        private static void CheckDebugger()
        {
            if (!System.Environment.UserInteractive && !System.Diagnostics.Debugger.IsAttached)
            {
                System.Threading.Thread.Sleep(15000);
            }
        }
        
        /// <summary>
        /// Initializes timer, which is used to schedule engine activity at defined intervals.
        /// </summary>
        private void InitializeScheduler()
        {
            _timer = new Timer();
            _timer.Interval = 1000;
            _timer.Elapsed += new ElapsedEventHandler(this.timer_Elapsed);
        }

        /// <summary>
        /// Begin engine activity.
        /// </summary>
        public void Run()
        {
            if (_timer == null)
            {
                throw new ApplicationException("Scheduler was not initialized. Invoke InitializeScheduler before trying to start engine.");
            }
            
            _timer.Start();

            LogManager.Info("Engine started");

            if (Environment.UserInteractive)
            {
                ConsoleKeyInfo consoleKeyInfo;
                Console.TreatControlCAsInput = true;
                Console.WriteLine("Press Control+C to exit the application.");
                
                while (true)
                {                    
                    consoleKeyInfo = Console.ReadKey(true);
                    if (((consoleKeyInfo.Modifiers & ConsoleModifiers.Control) > 0) &&
                        (consoleKeyInfo.Key == ConsoleKey.C))
                    {
                        break;
                    }
                }

                Stop();

                Console.WriteLine("Application stopped at user request.");
            }
        }

        /// <summary>
        /// Stops engine activity.
        /// </summary>
        public void Stop()
        {            
            _timer.Stop();

            LogManager.Info("Engine stopped");            
        }

        /// <summary>
        /// Forces cache update and walk through subscribers.
        /// </summary>
		public void Trigger()
		{
			_timer.Stop();
			timer_Elapsed(null, null);
		}

        /// <summary>
        /// Triggers cache updates and walk through subscribers.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {            
            CheckCache();
            Walk();            
        }

        /// <summary>
        /// Walks through all the feeds and updates ones those outdated.
        /// </summary>
        private void CheckCache()
        {
            // Check if cache needs to be updated
            foreach (Feed feed in Configuration.Instance.Feeds)
            {
                if (DateTime.Now >= feed.NextSync)
                {
                    Cache.Instance.UpdateCache(feed);
                    Configuration.Instance.SaveUpdates();
                }
            }
        }

        /// <summary>
        /// Walk through all the subscribers and check if any action needs to be performed.
        /// </summary>
        private void Walk()
        {
            bool actionsUpdated = false;
            foreach (Subscriber subscriber in Configuration.Instance.Subscribers)
            {
                if (subscriber.Enabled)
                {
                    foreach (Action action in subscriber.Actions)
                    {
                        if (DateTime.Now > action.NextAction)
                        {
                            action.LastSent = DateTime.Now;
                            actionsUpdated = true;

                            PreparePacket(subscriber, action);

                            LogManager.Info("Perform action for " + subscriber.Name);
                            LogManager.Info("Next action for " + subscriber.Name + " - " + action.NextAction.ToString());
                        }
                    }
                }
            }

            if (actionsUpdated)
            {
                Configuration.Instance.SaveUpdates();
            }
        }        

        public void PreparePacket(Subscriber subscriber, Action action)
        {
            if (subscriber == null)
                throw new ArgumentNullException("subscriber");

            if (action == null)
                throw new ArgumentNullException("action");            

            string feedContent = Cache.Instance.GetFeedContent(action.Feed.Name);
            
            DaySpanList daySpanList = new DaySpanList();
            daySpanList.LoadXmlFromFeedContent(feedContent);

            foreach (DaySpan daySpan in daySpanList)
            {
                if (daySpan.Matches(action))
                {                    
                    Packet packet = new Packet();
                    packet.LoadDocument(action.Feed, daySpan, subscriber.Transliterate);
                    SendPacket(subscriber, packet);

                    LogManager.Info("Sent weather to " + subscriber.Name);
                }
            }   
        }

        /// <summary>
        /// Send packet to the subscriber.
        /// </summary>
        /// <param name="subscriber">Subscriber to which the packet should be send to.</param>
        /// <param name="packet">Packet to be send.</param>
        private void SendPacket(Subscriber subscriber, Packet packet)
        {
            if (subscriber == null)            
                throw new ArgumentNullException("subscriber");                
            
            if (packet == null)            
                throw new ArgumentNullException("packet");            

            try
            {
                MailMessage message = new MailMessage(subscriber.Email, subscriber.Email, "info", packet.Item);
                SmtpClient client = new SmtpClient(Configuration.Instance.Smtp.Address);

                client.Credentials = GetCredentials();
                
                // client.Send(message);
                LogManager.Info("Sent packet " + packet.ToString());
            }
            catch (SmtpFailedRecipientsException ex)
            {
                LogManager.Error("Error while sending packet for subscriber " + subscriber.Name + " " + ex.ToString());
            }
            catch (SmtpException ex)
            {
                LogManager.Error("Error while sending packet for subscriber " + subscriber.Name + " " + ex.ToString());
            }
        }

        /// <summary>
        /// Return network credentials for accessing the smtp server from configuration.
        /// </summary>
        /// <returns>Network credentials.</returns>
        private ICredentialsByHost GetCredentials()
        {
            Smtp.AuthenticationType auth = Configuration.Instance.Smtp.Authentication;
            NetworkCredential credentials = null;

            if (Smtp.AuthenticationType.Secured == auth)
            {
                credentials = new NetworkCredential(
                    Configuration.Instance.Smtp.Username, Configuration.Instance.Smtp.Password);
            }
            else
            {
                credentials = CredentialCache.DefaultNetworkCredentials;
            }

            return credentials;
        }
    }
}